package io.wins.vertx.web;

import java.util.function.Consumer;

import com.google.common.net.HttpHeaders;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.Message;
import io.vertx.core.eventbus.ReplyException;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.auth.User;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.wins.vertx.verticle.api.CarouselApiVerticle;
import io.wins.vertx.verticle.api.ClassApiVerticle;
import io.wins.vertx.verticle.api.CommentApiVerticle;
import io.wins.vertx.verticle.api.ConcertApiVerticle;
import io.wins.vertx.verticle.api.CustomerApiVerticle;
import io.wins.vertx.verticle.api.GoodsApiVerticle;
import io.wins.vertx.verticle.api.PurchaseApiVerticle;
import io.wins.vertx.verticle.api.RoleApiVerticle;
import io.wins.vertx.verticle.api.RootApiVerticle;
import io.wins.vertx.verticle.api.SeatApiVerticle;
import io.wins.vertx.verticle.api.UserApiVerticle;

public class ServerResource implements Resource {

    private Vertx vertx;
    private ServerProperties serverProperties;
    private final static Logger logger = LoggerFactory.getLogger(ServerResource.class);

    public static ServerResource create(final Vertx vertx, final ServerProperties serverProperties) {
        final ServerResource serverResource = new ServerResource();
        serverResource.vertx = vertx;
        serverResource.serverProperties = serverProperties;
        return serverResource;
    }

    @Override
    public Resource init(final Router router) {
        logger.info("Deploying server resources...");

        router.get("/api/checkId").handler(this::checkId);
        router.get("/api/logout").handler(this::logout);
        router.post("/api/login").handler(this::login);
        router.post("/api/register").handler(this::register);

        router.get("/api/customer").handler(this::listCustomer);
        router.get("/api/customer/:id").handler(this::loadCustomer);
        router.post("/api/customer/:id").handler(this::updateCustomer);
        router.get("/api/address").handler(this::loadAddress);
        router.post("/api/address").handler(this::saveAddress);
        router.post("/api/address/:id").handler(this::saveAddress);
        router.delete("/api/address/:id").handler(this::deleteAddress);

//        router.get("/api/user").handler(this::listUser); // show list
//        router.get("/api/user/:id"); // show by id
//        router.post("/api/user"); // create
//        router.post("/api/user/:id"); // update
//        router.delete("/api/user/:id"); // delete

        router.get("/api/root").handler(this::listRoot); // show list
        router.get("/api/root/:id").handler(this::getRoot); // show by id
        router.post("/api/root").handler(this::createRoot); // create
        router.post("/api/root/:id").handler(this::updateRoot); // update
        router.delete("/api/root/:id").handler(this::deleteRoot); // delete

        router.get("/api/role").handler(this::listRole); // show list
        router.get("/api/role/:id").handler(this::getRole); // show by id
        router.post("/api/role").handler(this::createRole); // create
        router.post("/api/role/:id").handler(this::updateRole); // update
        router.delete("/api/role/:id").handler(this::deleteRole); // delete

        router.get("/api/concert").handler(this::listConcert); // show list
        router.get("/api/concert/:id").handler(this::getConcert); // show by id
        router.post("/api/concert").handler(this::createConcert); // create
        router.post("/api/concert/:id").handler(this::updateConcert); // update
        router.put("/api/concert/:id").handler(this::publishConcert); // publish
        router.delete("/api/concert/:id").handler(this::deleteConcert); // delete
        router.get("/api/concertTime/:pid").handler(this::concertTime);
        router.get("/api/faceValues/:pid").handler(this::faceValues);

        router.get("/api/comment").handler(this::listComments); // list
        router.post("/api/comment").handler(this::createComments); // create
        router.delete("/api/comment/:id").handler(this::deleteComments); // delete
        router.post("/api/comments").handler(this::reviewComments); // create

        router.post("/api/tag").handler(this::createTag); // create
        router.post("/api/tags").handler(this::listTags); // load

        router.get("/api/goods").handler(this::listGoods); // show list
        router.get("/api/goods/:id").handler(this::getGoods); // show by id
        router.post("/api/goods").handler(this::createGoods); // create
        router.post("/api/goods/:id").handler(this::updateGoods); // update
        router.put("/api/goods/:id").handler(this::publishGoods); // publish
        router.delete("/api/goods/:id").handler(this::deleteGoods); // delete

        router.get("/api/purchase").handler(this::listPurchase); // show list
        router.get("/api/purchase/:id").handler(this::loadPurchase); // show by id
        router.post("/api/purchase").handler(this::createPurchase); // create
        router.post("/api/purchase/:id").handler(this::updatePurchase); // update
        router.delete("/api/purchase/:id").handler(this::deletePurchase); // delete

        router.get("/api/carousel").handler(this::listCarousel); // show list
        router.get("/api/carousel/:id").handler(this::getCarousel); // show by id
        router.post("/api/carousel").handler(this::createCarousel); // create
        router.post("/api/carousel/:id").handler(this::updateCarousel); // update
        router.delete("/api/carousel/:id").handler(this::deleteCarousel); // delete

        router.get("/api/class").handler(this::listClass); // show list
        router.get("/api/class/:id").handler(this::loadClass); // show by id
        router.post("/api/class").handler(this::createClass); // create
        router.post("/api/class/:id").handler(this::updateClass); // update
        router.delete("/api/class/:id").handler(this::deleteClass); // delete

        router.get("/api/seat").handler(this::listSeat); // show list
        router.get("/api/seat/:id").handler(this::loadSeat); // show by id
        router.post("/api/seat").handler(this::createSeat); // create
        router.post("/api/seat/:id").handler(this::updateSeat); // update
        router.delete("/api/seat/:id").handler(this::deleteSeat); // delete

        logger.info("Deploy server done.");
        return this;
    }

    private void deleteComments(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CommentApiVerticle.REST_ADDRESS, CommentApiVerticle.DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }


    private void listComments(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CommentApiVerticle.REST_ADDRESS, CommentApiVerticle.LIST),
        this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createComments(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CommentApiVerticle.REST_ADDRESS, CommentApiVerticle.CREATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void reviewComments(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CommentApiVerticle.REST_ADDRESS, CommentApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }


    private void updateCustomer(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CustomerApiVerticle.REST_ADDRESS, CustomerApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void loadCustomer(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CustomerApiVerticle.REST_ADDRESS, CustomerApiVerticle.LOAD),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listCustomer(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CustomerApiVerticle.REST_ADDRESS, CustomerApiVerticle.LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteAddress(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CustomerApiVerticle.REST_ADDRESS, CustomerApiVerticle.ADDRESS_DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void saveAddress(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CustomerApiVerticle.REST_ADDRESS, CustomerApiVerticle.ADDRESS_SAVE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void loadAddress(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CustomerApiVerticle.REST_ADDRESS, CustomerApiVerticle.ADDRESS_LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void concertTime(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.CONCERT_TIME),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void faceValues(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.FACE_VALUES),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listTags(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.LIST_TAGS),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createTag(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.CREATE_TAG),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteClass(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ClassApiVerticle.REST_ADDRESS, ClassApiVerticle.DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateClass(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ClassApiVerticle.REST_ADDRESS, ClassApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createClass(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ClassApiVerticle.REST_ADDRESS, ClassApiVerticle.CREATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void loadClass(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ClassApiVerticle.REST_ADDRESS, ClassApiVerticle.LOAD),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listClass(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ClassApiVerticle.REST_ADDRESS, ClassApiVerticle.LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteSeat(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(SeatApiVerticle.REST_ADDRESS, SeatApiVerticle.DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateSeat(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(SeatApiVerticle.REST_ADDRESS, SeatApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createSeat(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(SeatApiVerticle.REST_ADDRESS, SeatApiVerticle.CREATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void loadSeat(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(SeatApiVerticle.REST_ADDRESS, SeatApiVerticle.LOAD),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listSeat(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(SeatApiVerticle.REST_ADDRESS, SeatApiVerticle.LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deletePurchase(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(PurchaseApiVerticle.REST_ADDRESS, PurchaseApiVerticle.DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updatePurchase(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(PurchaseApiVerticle.REST_ADDRESS, PurchaseApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createPurchase(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(PurchaseApiVerticle.REST_ADDRESS, PurchaseApiVerticle.CREATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void loadPurchase(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(PurchaseApiVerticle.REST_ADDRESS, PurchaseApiVerticle.LOAD),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listPurchase(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(PurchaseApiVerticle.REST_ADDRESS, PurchaseApiVerticle.LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteCarousel(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CarouselApiVerticle.REST_ADDRESS, CarouselApiVerticle.DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateCarousel(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CarouselApiVerticle.REST_ADDRESS, CarouselApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createCarousel(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CarouselApiVerticle.REST_ADDRESS, CarouselApiVerticle.CREATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void getCarousel(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CarouselApiVerticle.REST_ADDRESS, CarouselApiVerticle.LOAD),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listCarousel(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(CarouselApiVerticle.REST_ADDRESS, CarouselApiVerticle.LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteRoot(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RootApiVerticle.ROOT_REST_ADDRESS, RootApiVerticle.DELETE_ROOT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateRoot(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RootApiVerticle.ROOT_REST_ADDRESS, RootApiVerticle.UPDATE_ROOT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createRoot(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RootApiVerticle.ROOT_REST_ADDRESS, RootApiVerticle.CREATE_ROOT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void getRoot(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RootApiVerticle.ROOT_REST_ADDRESS, RootApiVerticle.LOAD_ROOT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listRoot(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RootApiVerticle.ROOT_REST_ADDRESS, RootApiVerticle.LIST_ROOT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteRole(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RoleApiVerticle.ROLE_REST_ADDRESS, RoleApiVerticle.DELETE_ROLE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateRole(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RoleApiVerticle.ROLE_REST_ADDRESS, RoleApiVerticle.UPDATE_ROLE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createRole(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RoleApiVerticle.ROLE_REST_ADDRESS, RoleApiVerticle.CREATE_ROLE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void getRole(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RoleApiVerticle.ROLE_REST_ADDRESS, RoleApiVerticle.LOAD_ROLE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listRole(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(RoleApiVerticle.ROLE_REST_ADDRESS, RoleApiVerticle.LIST_ROLE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listUser(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(UserApiVerticle.USER_REST_ADDRESS, UserApiVerticle.LIST_USER),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void login(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(UserApiVerticle.USER_REST_ADDRESS, UserApiVerticle.LOGIN_USER),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void logout(final RoutingContext context) {

    }

    private void checkId(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(UserApiVerticle.USER_REST_ADDRESS, UserApiVerticle.CHECK_ID),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void register(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(UserApiVerticle.USER_REST_ADDRESS, UserApiVerticle.REGISTER_USER),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteConcert(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.DELETE_CONCERT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateConcert(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.UPDATE_CONCERT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void publishConcert(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.PUBLISH_CONCERT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createConcert(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.CREATE_CONCERT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void getConcert(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.LOAD_CONCERT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listConcert(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(ConcertApiVerticle.CONCERT_REST_ADDRESS, ConcertApiVerticle.LIST_CONCERT),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void deleteGoods(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(GoodsApiVerticle.REST_ADDRESS, GoodsApiVerticle.DELETE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void updateGoods(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(GoodsApiVerticle.REST_ADDRESS, GoodsApiVerticle.UPDATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void createGoods(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(GoodsApiVerticle.REST_ADDRESS, GoodsApiVerticle.CREATE),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void getGoods(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(GoodsApiVerticle.REST_ADDRESS, GoodsApiVerticle.LOAD),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void listGoods(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(GoodsApiVerticle.REST_ADDRESS, GoodsApiVerticle.LIST),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private void publishGoods(final RoutingContext context) {
        this.<JsonObject>processRequest(String.format(GoodsApiVerticle.REST_ADDRESS, GoodsApiVerticle.PUBLISH),
            this.requestMessage(context), this.resultHandler(context, this.responConsumer(context)));
    }

    private Consumer<JsonObject> responConsumer(final RoutingContext context) {
        return resultBody -> {
            context.response()
                .setStatusCode(200)
                .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_CHARSET_UTF_8)
                .end(resultBody.encode());
        };
    }

//    private <T> void processRequest(
//            final String operationId,
//            final RoutingContext routingContext, final Handler<AsyncResult<Message<T>>> handler) {
//        if (serverProperties.isValidateRequest()) {
//            processRequestWithValidation(operationId, routingContext, handler);
//        } else {
//            processRequest(operationId, cpmMessage(routingContext, requestMessage(routingContext)), handler);
//        }
//    }

//    private <T> void processRequestWithValidation(final String operationId, final RoutingContext routingContext,
//                                                  final Handler<AsyncResult<Message<T>>> handler) {
//        final DeliveryOptions deliveryOptions = ServerResource.SERVER_VALIDATION_DELIVERY_OPTIONS.get(operationId);

//        final String token = routingContext.getCookie()

//        vertx.eventBus().<JsonObject>send(JsonValidator.ADDRESS_VALIDATE_JSON,
//                requestMessage(routingContext), deliveryOptions, ar -> {
//                    if (ar.succeeded()) {
//                        processRequest(operationId, respondMessage(routingContext, ar.result().body()), handler);
//                    } else {
//                        routingContext.fail(validationFailure(ar.cause()));
//                    }
//                });
//    }

    private <T> void processRequest(
        final String address, final JsonObject message, final Handler<AsyncResult<Message<T>>> handler) {
        this.vertx.eventBus().send(address, message, handler);
    }

    private JsonObject requestMessage(final RoutingContext routingContext) {
        final JsonObject message = new JsonObject();
        message.put("param", this.multiMapToJsonObject(routingContext.request().params()));
        message.put("body", routingContext.getBody() != null && routingContext.getBody().length() > 0 ?
            routingContext.getBodyAsJson() : new JsonObject());
        return message;
    }

    private JsonObject multiMapToJsonObject(final MultiMap multiMap) {
        final JsonObject jsonObject = new JsonObject();
        multiMap.forEach(entry -> jsonObject.put(entry.getKey(), entry.getValue()));
        return jsonObject;
    }

    private JsonObject respondMessage(final RoutingContext routingContext, final JsonObject json) {
        final User user = routingContext.user();
        final JsonObject param = json.getJsonObject("param");
        final JsonObject body = json.getJsonObject("body");
        return new JsonObject()
            .mergeIn(body).mergeIn(param).put("domainId", user.principal().getString("sub"));
    }

    private Throwable validationFailure(final Throwable cause) {
        if (cause instanceof ReplyException && ((ReplyException) cause).failureCode() >= 0) {
            final ErrorEntity.Builder builder = ErrorEntity.builder(ErrorEntity.Code.VALIDATION_ERROR);

            final JsonObject validationErrors = new JsonObject(cause.getMessage());
            validationErrors.getJsonArray("errors").forEach(e -> {
                final JsonObject validationError = (JsonObject) e;
                final String pointer = validationError.getJsonObject("instance").getString("pointer");
                final String message = validationError.getString("message");
                final ErrorEntity.Location location = this.resolveErrorLocation(pointer);
                final String name = this.resolveErrorParamName(pointer);

                builder.detail(message, location, name);
            });

            return new WebApplicationException(400, builder.build());
        } else {
            return cause;
        }
    }

    private ErrorEntity.Location resolveErrorLocation(final String pointer) {
        if (pointer.startsWith("/body")) {
            return ErrorEntity.Location.BODY;
        }
        if (pointer.startsWith("/param")) {
            return ErrorEntity.Location.PATH;
        }
        // should never reach here
        throw new IllegalStateException("Cannot resolve error location.");
    }

    private String resolveErrorParamName(final String pointer) {
        if (pointer.equals("/body")) {
            return "body";
        }
        if (pointer.equals("/param")) {
            return "path";
        }
        if (pointer.startsWith("/body/")) {
            return pointer.substring(5);
        }
        if (pointer.startsWith("/param/")) {
            return pointer.substring(7);
        }
        // should never reach here
        throw new IllegalStateException("Cannot resolve error parameter name.");
    }

    private <T> Handler<AsyncResult<Message<T>>> resultHandler(final RoutingContext routingContext, final Consumer<T> c) {
        return ar -> {
            if (ar.succeeded()) {
                c.accept(ar.result().body());
            } else {
                this.handleFailure(ar.cause(), routingContext);
            }
        };
    }

    private void handleFailure(final Throwable t, final RoutingContext routingContext) {
        routingContext.fail(new WebApplicationException(t.getMessage(), this.getStatusCode(t)));
    }

    private int getStatusCode(final Throwable t) {
        if (!(t instanceof ReplyException)) {
            return 500;
        }
        final ReplyException re = (ReplyException) t;
        switch (re.failureType()) {
            case NO_HANDLERS:
                return 500;
            case RECIPIENT_FAILURE:
                switch (re.failureCode()) {
                    case 1: // internal error
                        return 500;
                    case 2: // resource not found
                        return 404;
                    case 3: // timeout exception
                        return 503;
                    default:
                        return 500;
                }
            case TIMEOUT:
                return 503;
            default:
                return 500;
        }
    }
}
